package toolkit

import (
	"errors"
	"gitee.com/hongzhaomin/hzm-common-go/coll"
	"gitee.com/hongzhaomin/hzm-common-go/easylog"
	"gitee.com/hongzhaomin/hzm-common-go/strutil"
	"reflect"
)

// Bean2Map 结构体转map, 可以指定字段名称进行强制转换
// structVal - 结构体对象
// forceConvFields - 强制转换的字段名称列表
func Bean2Map(structVal any, forceConvFields ...string) map[string]any {
	return Bean2Map4CustomStrategy(structVal, nil, forceConvFields...)
}

// Bean2MapHump 结构体转map, 可以指定字段名称进行强制转换，并将字段名称转换为 “小驼峰” 命名方式
// structVal - 结构体对象
// forceConvFields - 强制转换的字段名称列表
func Bean2MapHump(structVal any, forceConvFields ...string) map[string]any {
	return Bean2Map4CustomStrategy(structVal, strutil.FirstLetter2Lower, forceConvFields...)
}

// Bean2MapSnake 结构体转map, 可以指定字段名称进行强制转换，并将字段名称转换为 “小写下划线” 命名方式
// structVal - 结构体对象
// forceConvFields - 强制转换的字段名称列表
func Bean2MapSnake(structVal any, forceConvFields ...string) map[string]any {
	return Bean2Map4CustomStrategy(structVal, strutil.NameSnake, forceConvFields...)
}

// Bean2Map4CustomStrategy 结构体转map, 可以指定字段名称进行强制转换，可以自定义字段名称转换方式
// structVal - 结构体对象
// convertName - 自定义字段名称转换函数
// forceConvFields - 强制转换的字段名称列表
func Bean2Map4CustomStrategy(structVal any, convertName func(fileName string) string,
	forceConvFields ...string) map[string]any {
	resultMap := make(map[string]any)
	BeanFieldsForeach(structVal, func(structField reflect.StructField, rvField reflect.Value) {
		fieldName := structField.Name
		if !rvField.IsZero() || coll.ContainsIgnoreCase(forceConvFields, fieldName) {
			if convertName != nil {
				fieldName = convertName(fieldName)
			}
			resultMap[fieldName] = rvField.Interface()
		}
	})
	return resultMap
}

// GetZeroFieldNames 获取结构体零值字段名列表, 可以指定忽略的字段
// structVal - 结构体对象
// ignores - 忽略的字段名称列表
func GetZeroFieldNames(structVal any, ignores ...string) []string {
	return GetZeroFieldNames4CustomStrategy(structVal, nil, ignores...)
}

// GetZeroFieldNamesHump 获取结构体零值字段名列表, 可以指定忽略的字段，并将字段名称转换为 “小驼峰” 命名方式
// structVal - 结构体对象
// ignores - 忽略的字段名称列表
func GetZeroFieldNamesHump(structVal any, ignores ...string) []string {
	return GetZeroFieldNames4CustomStrategy(structVal, strutil.FirstLetter2Lower, ignores...)
}

// GetZeroFieldNamesSnake 获取结构体零值字段名列表, 可以指定忽略的字段，并将字段名称转换为 “小写下划线” 命名方式
// structVal - 结构体对象
// ignores - 忽略的字段名称列表
func GetZeroFieldNamesSnake(structVal any, ignores ...string) []string {
	return GetZeroFieldNames4CustomStrategy(structVal, strutil.NameSnake, ignores...)
}

// GetZeroFieldNames4CustomStrategy 获取结构体 “零值” 字段名列表, 可以指定忽略的字段，可以自定义字段名称转换方式
// structVal - 结构体对象
// convertName - 自定义字段名称转换函数
// forceExcludeFields - 强制排除（忽略）的字段名称列表
func GetZeroFieldNames4CustomStrategy(structVal any, convertName func(fileName string) string,
	forceExcludeFields ...string) []string {
	return GetFieldNames4IncludeTypeAndCustomStrategy(structVal, Zero, convertName, forceExcludeFields...)
}

// GetAllFieldNames4CustomStrategy 获取结构体 “所有” 字段名列表, 可以指定忽略的字段，可以自定义字段名称转换方式
// structVal - 结构体对象
// convertName - 自定义字段名称转换函数
// forceExcludeFields - 强制排除（忽略）的字段名称列表
func GetAllFieldNames4CustomStrategy(structVal any, convertName func(fileName string) string,
	forceExcludeFields ...string) []string {
	return GetFieldNames4IncludeTypeAndCustomStrategy(structVal, All, convertName, forceExcludeFields...)
}

// GetNotZeroFieldNames4CustomStrategy 获取结构体 “非零值” 字段名列表, 可以指定忽略的字段，可以自定义字段名称转换方式
// structVal - 结构体对象
// convertName - 自定义字段名称转换函数
// forceExcludeFields - 强制排除（忽略）的字段名称列表
func GetNotZeroFieldNames4CustomStrategy(structVal any, convertName func(fileName string) string,
	forceExcludeFields ...string) []string {
	return GetFieldNames4IncludeTypeAndCustomStrategy(structVal, NotZero, convertName, forceExcludeFields...)
}

// GetFieldNames4IncludeTypeAndCustomStrategy 获取结构体字段名列表, 可以指定零值、非零值类型的字段，可以指定忽略的字段，可以自定义字段名称转换方式
// structVal - 结构体对象
// FieldType - 字段类型（所有、零值、非零值）
// convertName - 自定义字段名称转换函数
// forceExcludeFields - 强制排除（忽略）的字段名称列表
func GetFieldNames4IncludeTypeAndCustomStrategy(structVal any, fieldType FieldType,
	convertName func(fileName string) string, forceExcludeFields ...string) []string {
	fieldNames := make([]string, 0)
	BeanFieldsForeach(structVal, func(structField reflect.StructField, rvField reflect.Value) {
		var isAdd bool
		switch fieldType {
		case All:
			isAdd = true
		case Zero:
			isAdd = rvField.IsZero()
		case NotZero:
			isAdd = !rvField.IsZero()
		}
		fieldName := structField.Name
		if isAdd {
			if convertName != nil {
				fieldName = convertName(fieldName)
			}
			if !coll.ContainsIgnoreCase(forceExcludeFields, fieldName) {
				fieldNames = append(fieldNames, fieldName)
			}
		}
	})
	return fieldNames
}

// FieldType 字段类型枚举
type FieldType uint8

const (
	All     FieldType = iota // 所有字段
	Zero                     // 零值字段
	NotZero                  // 非零值字段
)

// BeanFieldsForeach 将结构体字段反射进行循环自定义处理
// structVal - 结构体对象
// consumer - 自定义处理函数
//
//	函数会收到两个参数：
//		1、structField: 结构体字段对应的字段结构反射对象 reflect.StructField
//		2、rvField: 结构体字段对应的反射值 reflect.Value
func BeanFieldsForeach(structVal any, consumer func(structField reflect.StructField, rvField reflect.Value)) {
	var rv reflect.Value
	switch val := structVal.(type) {
	case reflect.Value:
		rv = reflect.Indirect(val)
	default:
		rv = reflect.Indirect(reflect.ValueOf(val))
		if rv.Kind() != reflect.Struct {
			panic(errors.New("source and target must be struct kind"))
		}
	}
	rt := rv.Type()
	for i := 0; i < rt.NumField(); i++ {
		structField := rt.Field(i)
		if strutil.IsNotBlank(structField.PkgPath) {
			// 非导出字段或嵌套字段
			continue
		}

		rtField := structField.Type
		if rtField.Kind() == reflect.Ptr {
			rtField = rtField.Elem()
		}

		if structField.Anonymous {
			if rtField.Kind() == reflect.Struct {
				BeanFieldsForeach(rv.Field(i), consumer)
			}
			continue
		}
		consumer(structField, rv.Field(i))
	}
}

// Map2Bean map转结构体
// 通过json方式转换，底层会通过反射自动给结构体加上json的tag
// 返回的是该泛型结构体的指针
func Map2Bean[T comparable](mapVal map[string]any) *T {
	jsonStr := ToJson(mapVal)
	return Json2Struct[T](jsonStr)
}

// NotExistTag 根据结构体反射类型和tag名称，判断该结构体上是否存在此tag
// return true - 不存在
//
//	false - 存在
func NotExistTag(rtStruct reflect.Type, tag string) bool {
	if rtStruct.Kind() == reflect.Ptr {
		rtStruct = rtStruct.Elem()
	}
	structName := rtStruct.Name()
	if rtStruct.Kind() != reflect.Struct {
		panic(errors.New("must be struct kind"))
	}
	for i := 0; i < rtStruct.NumField(); i++ {
		field := rtStruct.Field(i)
		if strutil.IsNotBlank(field.PkgPath) {
			// 字段不可导出
			continue
		}
		rtField := field.Type
		if rtField.Kind() == reflect.Ptr {
			rtField = rtField.Elem()
		}
		if rtField.Kind() == reflect.Struct {
			// 和自身结构体类型不相同
			if rtStruct.PkgPath() == rtField.PkgPath() {
				if rtField.Name() == structName {
					easylog.Warn("结构体[%s]中字段[%s]的类型为[%s]类型指针，建议自定义添加tag",
						structName, field.Name, structName)
					return false
				}
			}
			notExist := NotExistTag(rtField, tag)
			if !notExist {
				return false
			}
		}
		if _, ok := field.Tag.Lookup(tag); ok {
			return false
		}
	}
	return true
}

// CopyPropertiesByJson 结构体的属性赋值，将source赋值到target上
// 底层使用json的方式实现
// 注意: 1、target必须是结构体的指针，否则该方法不生效
//
//	2、两个结构体内的属性名称一样的才会赋值成功，否则名称不一样的不生效
func CopyPropertiesByJson(source any, target any) {
	sourceJson := ToJson3(source)
	Json2Obj(sourceJson, target)
}

// CopyStruct 结构体的属性赋值，将source赋值到泛型Struct的结构体上
// 底层使用结构体反射的方式实现
// 注意: 两个结构体内的属性名称一样的才会赋值成功，否则名称不一样的不生效
func CopyStruct[Struct any](source any) *Struct {
	rt := reflect.TypeOf((*Struct)(nil)).Elem()
	if rt.Kind() != reflect.Struct {
		panic("泛型类型必须为结构体")
	}
	sPtr := new(Struct)
	CopyProperties(source, sPtr)
	return sPtr
}

// CopyProperties 结构体的属性赋值，将source赋值到target上，可以指定字段不进行赋值
// 底层使用结构体反射的方式实现
// 注意: 两个结构体内的属性名称一样的才会赋值成功，否则名称不一样的不生效
// source - 源对象
// target - 目标对象
// ignores - 忽略的字段名称列表
func CopyProperties(source any, target any, ignores ...string) {
	if IsNil(source) || IsNil(target) {
		panic(errors.New("source and target must not nil"))
	}
	if reflect.ValueOf(target).Kind() != reflect.Ptr {
		panic(errors.New("target must be point kind"))
	}
	rvSou := reflect.Indirect(reflect.ValueOf(source))
	rvTar := reflect.Indirect(reflect.ValueOf(target))
	if rvSou.Kind() != reflect.Struct || rvTar.Kind() != reflect.Struct {
		panic(errors.New("source and target must be struct kind"))
	}

	rtTar := rvTar.Type()
	for i := 0; i < rtTar.NumField(); i++ {
		rtTarStructField := rtTar.Field(i)
		tarFieldName := rtTarStructField.Name
		// 判断是否忽略该属性的拷贝
		if coll.ContainsIgnoreCase(ignores, tarFieldName) {
			continue
		}
		if strutil.IsNotBlank(rtTarStructField.PkgPath) {
			// 非导出字段
			continue
		}
		// 根据目标对象的属性名找到源对象属性
		rtSouStructField, ok := rvSou.Type().FieldByName(tarFieldName)
		if !ok {
			continue
		}
		if strutil.IsNotBlank(rtSouStructField.PkgPath) {
			// 非导出字段
			continue
		}
		// 源对象属性类型要和目标对象属性类型一致
		if rtSouStructField.Type.Kind() != rtTarStructField.Type.Kind() {
			continue
		}
		// 指针类型再转换为指针指向的值的类型，再次对比一次
		rvSouField := reflect.Indirect(rvSou.FieldByName(tarFieldName))
		rvTarField := reflect.Indirect(rvTar.Field(i))
		if rvSouField.Kind() != rvTarField.Kind() {
			continue
		}
		switch rvSouField.Kind() {
		case reflect.Struct:
			// 创建对象指针
			rvTarFieldCopied := reflect.New(rvTarField.Type())
			CopyProperties(rvSouField.Interface(), rvTarFieldCopied.Interface())
			rvTarField.Set(rvTarFieldCopied.Elem())
		case reflect.Map:
			rvTarFieldMap := reflect.MakeMapWithSize(rvTarField.Type(), rvSouField.Len())
			for _, key := range rvSouField.MapKeys() {
				rvTarFieldMap.SetMapIndex(key, rvSouField.MapIndex(key))
			}
			rvTarField.Set(rvTarFieldMap)
		case reflect.Slice:
			rtSouEle := rvSouField.Type().Elem()
			rtTarEle := rvTarField.Type().Elem()
			if rtSouEle.Kind() != rtTarEle.Kind() {
				break
			}
			tarFieldIsPtr := false
			if rtTarEle.Kind() == reflect.Ptr {
				rtSouEle = rtSouEle.Elem()
				rtTarEle = rtTarEle.Elem()
				if rtSouEle.Kind() != rtTarEle.Kind() {
					break
				}
				tarFieldIsPtr = true
			}
			if rtSouEle.Kind() == reflect.Struct {
				// 支持切片元素类型为结构体的值拷贝
				newSlice := reflect.MakeSlice(rvTarField.Type(), 0, rvSouField.Len())
				for i := 0; i < rvSouField.Len(); i++ {
					rvSouEleField := rvSouField.Index(i)
					rvTarFieldCopied := reflect.New(rtTarEle)
					CopyProperties(rvSouEleField.Interface(), rvTarFieldCopied.Interface())
					if tarFieldIsPtr {
						// 支持元素为指针
						newSlice = reflect.Append(newSlice, rvTarFieldCopied)
					} else {
						newSlice = reflect.Append(newSlice, rvTarFieldCopied.Elem())
					}
				}
				rvTarField.Set(newSlice)
				break
			}
			newSlice := reflect.MakeSlice(rvTarField.Type(), rvSouField.Len(), rvSouField.Len())
			reflect.Copy(newSlice, rvSouField)
			rvTarField.Set(newSlice)
		default:
			rvTarField.Set(rvSouField)
		}
	}
}

// IsNil 判断接口类型的a参数是否为nil
func IsNil(a any) bool {
	// 空接口判断是否为nil，必须类型和值都为nil才会返回真
	// 大多数情况是：type不为nil, value为nil
	// 那就需要继续判断了，使用反射判断
	if a == nil {
		return true
	}
	rv := reflect.ValueOf(a)
	if !rv.IsValid() {
		return true
	}
	return rv.IsZero()
}
